﻿using System;
using System.Collections.Generic;
using System.Linq;
using Softelligent.Zintra.ClientSDK;
using Softelligent.Zintra.ClientSDK.Contacts;
using Softelligent.Zintra.ClientSDK.Resources;
using Softelligent.Zintra.Synchronizations.ContactsExpressConsole.Properties;

namespace Softelligent.Zintra.Synchronizations.ContactsExpressConsole
{
    public class ConsoleManager
    {
        private const string AddStatus = "add";
        private const string ChangeStatus = "change";
        private const string RemoveStatus = "remove";
        private readonly int batchSize;
        private readonly Repository<Contact> embeddedRepository;
        private readonly ContactRepository repository;
        private readonly ContactMapper mapper;

        public ConsoleManager()
        {
            batchSize = Settings.Default.BatchSize;
            embeddedRepository = ResourceStorage.For<Contact>().At(string.Concat(AppDomain.CurrentDomain.BaseDirectory, @"\Storage"));
            repository = new ContactRepository();
            mapper = new ContactMapper();
        }

        public bool HasSubscription()
        {
            return embeddedRepository.GetSubscription() != null;
        }

        public void StoreSubscription(string token)
        {
            embeddedRepository.StoreSubscription(token);
        }

        public void Store()
        {
            StoreDeleted();
            StoreAddedOrChanged();
        }

        private void StoreDeleted()
        {
            var pageCount = 1;
            for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)
            {
                var addedOrChanged = embeddedRepository.GetAddedOrChangedIds(pageNumber);
                pageCount = addedOrChanged.PageCount;
                var deletedIds = repository.FetchNotExisting(addedOrChanged.Items);
                foreach (var deletedId in deletedIds)
                {
                    embeddedRepository.StoreRemoved(deletedId);
                }
            }
        }

        private void StoreAddedOrChanged()
        {
            var pageCount = 1;
            for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)
            {
                var batch = repository.FetchAll(batchSize, pageNumber);
                pageCount = batch.Count;
                foreach (var contact in batch.Items)
                {
                    var data = mapper.Map(contact);
                    embeddedRepository.StoreChanged(data, data.Id);
                }
            }
        }

        public void Store(string status, string id)
        {
            if (string.IsNullOrEmpty(status) || string.IsNullOrEmpty(id))
            {
                return;
            }

            if (string.Compare(status, AddStatus, true) == 0)
            {
                var entity = repository.Fetch(id);
                if (entity != null)
                {
                    var data = mapper.Map(entity);
                    embeddedRepository.StoreAdded(data, data.Id);
                }
            }
            else if (string.Compare(status, ChangeStatus, true) == 0)
            {
                var entity = repository.Fetch(id);
                if (entity != null)
                {
                    var data = mapper.Map(entity);
                    embeddedRepository.StoreChanged(data, data.Id);
                }
            }
            else if (string.Compare(status, RemoveStatus, true) == 0)
            {
                embeddedRepository.StoreRemoved(id);
            }
        }

        public void Sync()
        {
            var subscription = embeddedRepository.GetSubscription();
            var resource = ResourceContacts.UsingJSON.AuthenticateWith(subscription.Token);
            SyncTo(resource);
            SyncBack(resource);
        }

        private void SyncTo(ContactResource resource)
        {
            var pageCount = 1;
            for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)
            {
                var page = embeddedRepository.GetChanges(pageNumber);
                pageCount = page.Count;
                resource.SaveChanges(new ContactsChangeBatch
                {
                    ChangedOrAdded = page.AddedOrChanged.ToList(),
                    Removed = page.Removed.ToList()
                });
            }
        }

        private void SyncBack(ContactResource resource)
        {
            var batch = resource.GetChanges();
            while (batch.Count > 0)
            {
                SyncBack(resource, batch);
                batch = batch.Next();
            }
            // To handle the last getChanges tell zintra nothing has been processed
        }

        private void SyncBack(ContactResource resource, ContactsBatch batch)
        {
            var mappings = new List<Mapping>();
            try
            {
                foreach (var data in batch.Added)
                {
                    var contact = mapper.Map(data);
                    var entity = repository.Save(contact);
                    mappings.Add(new Mapping
                    {
                        Id = entity.Id.ToString(),
                        ExternalId = data.ExternalId
                    });
                }
                foreach (var data in batch.Changed)
                {
                    var contact = mapper.Map(data);
                    var entity = repository.Save(contact);
                    mappings.Add(new Mapping
                    {
                        Id = entity.Id.ToString(),
                        ExternalId = data.ExternalId
                    });
                }
                foreach (var id in batch.Removed)
                {
                    repository.Delete(id);
                    mappings.Add(new Mapping
                    {
                        Id = id
                    });
                }
            }
            finally 
            {
                resource.SaveGetChangesProcessed(mappings.ToArray());
            }
        }
    }
}
